package net.avcompris.binding;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;

import java.lang.reflect.Method;

import net.avcompris.binding.annotation.XPath;

import com.avcompris.common.annotation.Nullable;

/**
 * an object to hold binding configuration for a given method. Similar to
 * declare an {@link XPath}-annotation and following
 * naming conventions for the method. 
 * 
 * @author David Andrianavalontsalama
 */
public class MethodBinding {

	private final ClassBinding classBinding;

	/**
	 * constructor with a method already loaded.
	 */
	public MethodBinding(
			@Nullable final BindConfiguration configuration,
			final Method method) {

		this.method = nonNullArgument(method, "method");
		this.methodName = method.getName();
		this.paramTypes = method.getParameterTypes();

		classBinding = new ClassBinding(configuration,
				method.getDeclaringClass());
	}

	/**
	 * constructor with a class already loaded,
	 * but the name of a method and the types of its parameters only.
	 */
	public MethodBinding(
			@Nullable final BindConfiguration configuration,
			final Class<?> clazz,
			final String methodName,
			final Class<?>... paramTypes) {

		this.method = null;
		this.methodName = nonNullArgument(methodName, "methodName");
		this.paramTypes = nonNullArgument(paramTypes, "paramTypes");

		classBinding = new ClassBinding(configuration, clazz);
	}

	/**
	 * constructor with a class already loaded,
	 * all method names, and some given parameter types.
	 */
	public MethodBinding(
			@Nullable final BindConfiguration configuration,
			final Class<?> clazz,
			final Class<?>... paramTypes) {

		this(configuration, clazz, "*", paramTypes);
	}

	/**
	 * constructor for all classes (<tt>className</tt> is <tt>"*"</tt> (= all)),
	 * with the name of a method and the types of its parameters.
	 */
	public MethodBinding(
			@Nullable final BindConfiguration configuration,
			final String methodName,
			final Class<?>... paramTypes) {

		this(configuration, "*", methodName, paramTypes);
	}

	/**
	 * constructor with the name of a class,
	 * the name of a method and the types of its parameters only.
	 */
	public MethodBinding(
			@Nullable final BindConfiguration configuration,
			final String className,
			final String methodName,
			final Class<?>... paramTypes) {

		this.method = null;
		this.methodName = nonNullArgument(methodName, "methodName");
		this.paramTypes = nonNullArgument(paramTypes, "paramTypes");

		classBinding = new ClassBinding(configuration, className);
	}

	/**
	 * constructor for all classes and all method names,
	 * with some given parameter types.
	 */
	public MethodBinding(
			@Nullable final BindConfiguration configuration,
			final Class<?>... paramTypes) {

		this(configuration, "*", "*", paramTypes);
	}

	@Nullable
	private final Method method;

	private final String methodName;

	private final Class<?>[] paramTypes;

	/**
	 * get the binding configuration.
	 */
	@Nullable
	public BindConfiguration getConfiguration() {

		return classBinding.getConfiguration();
	}

	/**
	 * get the name of the class.
	 */
	public String getBindingClassName() {

		return classBinding.getBindingClassName();
	}

	/**
	 * get the name of the method.
	 */
	public String getMethodName() {

		return methodName;
	}

	/**
	 * get the types of the method's parameters.
	 */
	public Class<?>[] getParamerTypes() {

		return paramTypes;
	}

	/**
	 * get the class itself, through a {@link ClassLoader}.
	 * If <tt>className</tt> was <tt>"*"</tt> (= all), this method returns
	 * <tt>null</tt>.
	 */
	public Class<?> getBindingClass(final ClassLoader classLoader) throws ClassNotFoundException {

		return classBinding.getBindingClass(classLoader);
	}

	/**
	 * get the class itself, through the default {@link ClassLoader}, that is,
	 * the one from the current {@link Thread}.
	 * If <tt>className</tt> was <tt>"*"</tt> (= all), this method returns
	 * <tt>null</tt>.
	 */
	public Class<?> getBindingClass() throws ClassNotFoundException {

		return classBinding.getBindingClass();
	}

	/**
	 * get the method itself, through a {@link ClassLoader}.
	 */
	public Method getMethod(final ClassLoader classLoader) throws ClassNotFoundException, SecurityException, NoSuchMethodException {

		nonNullArgument(classLoader, "classLoader");

		if (method != null) {

			return method;
		}

		final Class<?> clazz = getBindingClass(classLoader);

		return clazz.getMethod(methodName, paramTypes);
	}

	/**
	 * get the method itself, through the default {@link ClassLoader}, that is,
	 * the one from the current {@link Thread}.
	 */
	public Method getMethod() throws ClassNotFoundException, SecurityException, NoSuchMethodException {

		final ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();

		return getMethod(classLoader);
	}
}
